package com.atguigu.redislock.mylock;

import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;

import java.util.Arrays;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;

/**
 * RedisDistributedLock类实现了Lock接口，提供了一种基于Redis的分布式锁的实现方式
 * 它使用Redis的持久性和原子性来保证锁的互斥性和可用性
 *
 * 自研的redis分布式锁，实现了Lock接口
 */
//@Component 引入DistributedLockFactory工厂模式，从工厂获得即可
public class RedisDistributedLock implements Lock
{
    // 使用StringRedisTemplate来操作Redis
    private StringRedisTemplate stringRedisTemplate;
    // 锁的名称，对应Redis中的key
    private String lockName;
    // 当前锁的唯一标识，由UUID和当前线程ID组成，用于区分不同的锁实例
    private String uuidValue;
    // 锁的超时时间，防止死锁
    private long expireTime;
    
    
     /*public RedisDistributedLock(StringRedisTemplate stringRedisTemplate, String lockName)
    {
        this.stringRedisTemplate = stringRedisTemplate;
        this.lockName = lockName;
        this.uuidValue = IdUtil.simpleUUID()+":"+Thread.currentThread().getId();
        this.expireTime = 25L;
    }*/
    
    /**
     * 构造函数，初始化RedisDistributedLock实例
     * @param stringRedisTemplate 用于操作Redis的模板
     * @param lockName 锁的名称，用于在Redis中标识锁
     * @param uuid 唯一标识前缀，可以是用户指定的UUID
     */
    public RedisDistributedLock(StringRedisTemplate stringRedisTemplate, String lockName, String uuid)
    {
        this.stringRedisTemplate = stringRedisTemplate;
        this.lockName = lockName;
        this.uuidValue = uuid+":"+Thread.currentThread().getId();
        this.expireTime = 30L;
    }
    
    /**
     * 调用tryLock方法实现锁的获取
     */
    @Override
    public void lock()
    {
        tryLock();
    }
    
    /**
     * 尝试获取锁，如果无法立即获取锁，则返回false
     * @return 如果成功获取锁，返回true；否则返回false
     */
    @Override
    public boolean tryLock()
    {
        try {tryLock(-1L,TimeUnit.SECONDS);} catch (InterruptedException e) {e.printStackTrace();}
        return false;
    }
    
    /**
     * 尝试获取锁，如果在指定时间内无法获取锁，则返回false
     * @param time 尝试获取锁的最长时间
     * @param unit 时间单位
     * @return 如果成功获取锁，返回true；否则返回false
     * @throws InterruptedException 如果当前线程在等待时被中断
     */
    @Override
    public boolean tryLock(long time, TimeUnit unit) throws InterruptedException
    {
        if(time == -1L)
        {
            // 卢岑锁脚本，用于安全地尝试获取锁
            String script =
                    "if redis.call('exists',KEYS[1]) == 0 or redis.call('hexists',KEYS[1],ARGV[1]) == 1 then    " +
                            "redis.call('hincrby',KEYS[1],ARGV[1],1)    " +
                            "redis.call('expire',KEYS[1],ARGV[2])    " +
                            "return 1  " +
                            "else   " +
                            "return 0 " +
                            "end";
            System.out.println("lockName:"+lockName+"\t"+"uuidValue:"+uuidValue);
            // 尝试执行脚本以获取锁，如果失败则循环重试，并在每次重试之间短暂休眠
            while(!stringRedisTemplate.execute(new DefaultRedisScript<>(script,Boolean.class), Arrays.asList(lockName), uuidValue,String.valueOf(expireTime)))
            {
                try { TimeUnit.MILLISECONDS.sleep(60); } catch (InterruptedException e) { e.printStackTrace(); }
            }
            
            // 获取锁后，启动一个定时任务来自动续期
            // 新建一个后台扫描程序，来监视key目前的ttl，是否到问哦们规定的 1/2 1/3来实现自动续期
            renewExpire();
            return true;
        }
        return false;
    }
    
    /**
     * 释放锁
     */
    @Override
    public void unlock()
    {
        // 删除锁的脚本，用于安全地释放锁
        String script =
                "if redis.call('HEXISTS',KEYS[1],ARGV[1]) == 0 then    " +
                        "return nil  " +
                        "elseif redis.call('HINCRBY',KEYS[1],ARGV[1],-1) == 0 then    " +
                        "return redis.call('del',KEYS[1])  " +
                        "else    " +
                        "return 0 " +
                        "end";
        
        // nil = false , 1 = true ,0 = false
        
        // 执行脚本释放锁，如果锁不存在抛出异常
        Long flag = stringRedisTemplate.execute(new DefaultRedisScript<>(script, Long.class), Arrays.asList(lockName), uuidValue, String.valueOf(expireTime));
        if(null == flag)
        {
            throw new RuntimeException("this lock doesn't exists，/(ㄒoㄒ)/~~");
        }
    }
    
    /**
     * 自动续期方法，用于延长锁的过期时间，防止因过期导致的锁自动释放
     */
    private void renewExpire()
    {
        // 续期脚本，用于自动延长锁的过期时间
        String script =
                "if redis.call('HEXISTS',KEYS[1],ARGV[1]) == 1 then     " +
                        "return redis.call('expire',KEYS[1],ARGV[2]) " +
                        "else     " +
                        "return 0 " +
                        "end";
        
        // 使用定时任务定期执行续期操作
        new Timer().schedule(new TimerTask()
        {
            @Override
            public void run()
            {
                // 如果续期成功，则重新启动定时任务以继续续期
                if (stringRedisTemplate.execute(new DefaultRedisScript<>(script, Boolean.class), Arrays.asList(lockName), uuidValue, String.valueOf(expireTime)))
                {
                    renewExpire();
                }
            }
        },(this.expireTime * 1000)/3);// 每10秒执行一次
    }
    
    // 下面两个方法暂时用不到，不再重写
    // 下面两个方法暂时用不到，不再重写
    // 下面两个方法暂时用不到，不再重写
    // 下面两个方法暂时用不到，不再重写
    @Override
    public void lockInterruptibly() throws InterruptedException
    {
    
    }
    @Override
    public Condition newCondition()
    {
        return null;
    }
}
